﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using BeanboxSoftware.BeanMap;
using BeanboxSoftware.BeanMapTest;

namespace BeanMapTest.Unboxed
{
  [TestClass]
  public class MapKKKVTest
  {
    private Map<int, int, int, int> _map;

    [TestInitialize]
    public void SetUp()
    {
      _map = new Map<int, int, int, int>();
    }

    [TestMethod]
    public void SetGetRangeTest()
    {
      _map[1, 2, 3] = 4;
      var kkvChild = _map[1];
      var kvChild = _map[1, 2];
      var v = _map[1, 2, 3];

      Assert.AreEqual(4, kkvChild[2, 3]);
      Assert.AreEqual(4, kvChild[3]);
      Assert.AreEqual(4, v);

      kvChild = kkvChild[2];
      Assert.AreEqual(4, kvChild[3]);
    }

    [TestMethod]
    public void ChildSetTest()
    {
      var kkvChild = _map[1];
      kkvChild[2, 3] = 4;

      var kvChild = _map[1, 2];
      kvChild[4] = 5;

      Assert.AreEqual(4, _map[1, 2, 3]);
      Assert.AreEqual(5, _map[1, 2, 4]);
    }

    [TestMethod]
    public void ChildGetTest()
    {
      _map[1, 2, 3] = 4;

      var kkvChild = _map[1];
      var kvChild = _map[1, 2];

      Assert.AreEqual(4, kkvChild[2, 3]);
      Assert.AreEqual(4, kkvChild[2][3]);
      Assert.AreEqual(4, kvChild[3]);
    }

    [TestMethod]
    public void ContainsKeyTest()
    {
      Assert.IsFalse(_map.ContainsKey(1));
      Assert.IsFalse(_map.ContainsKey(1, 2));
      Assert.IsFalse(_map.ContainsKey(1, 2, 3));

      _map[1, 2, 3] = 4;

      Assert.IsTrue(_map.ContainsKey(1));
      Assert.IsTrue(_map.ContainsKey(1, 2));
      Assert.IsTrue(_map.ContainsKey(1, 2, 3));
    }

    [TestMethod]
    public void DefaultBehaviorTest()
    {
      Assert.AreEqual(KeyNotFoundBehavior.ThrowKeyNotFoundException, _map.KeyNotFoundBehavior);
      TestUtils.AssertExceptionThrown<KeyNotFoundException>(() => { int i = _map[1, 2, 3]; });
    }

    [TestMethod]
    public void DefaultValueTest()
    {
      TestUtils.AssertExceptionThrown<KeyNotFoundException>(() => { int i = _map[1, 2, 3]; });
      Assert.IsFalse(_map.ContainsKey(1, 2, 3));
      _map.DefaultValue = -1;
      Assert.AreEqual(KeyNotFoundBehavior.DefaultValue, _map.KeyNotFoundBehavior);
      Assert.AreEqual(-1, _map[1, 2, 3]);
      Assert.IsFalse(_map.ContainsKey(1, 2, 3));
    }

    [TestMethod]
    public void DefaultGenerationTest()
    {
      TestUtils.AssertExceptionThrown<KeyNotFoundException>(() => { int i = _map[1, 2, 3]; });
      Assert.IsFalse(_map.ContainsKey(1, 2, 3));
      _map.DefaultGeneration = (k1, k2, k3) => k1 + k2 + k3;
      Assert.AreEqual(KeyNotFoundBehavior.DefaultGeneration, _map.KeyNotFoundBehavior);
      Assert.AreEqual(6, _map[1, 2, 3]);
      Assert.IsTrue(_map.ContainsKey(1, 2, 3));
    }

    [TestMethod]
    public void ChildBehaviorTest()
    {
      var kkvChild = _map[1];
      var kvChild = _map[1, 2];

      TestUtils.AssertExceptionThrown<KeyNotFoundException>(() => { int i = kkvChild[2, 3]; });
      TestUtils.AssertExceptionThrown<KeyNotFoundException>(() => { int i = kvChild[23]; });

      _map.DefaultGeneration = (k1, k2, k3) => k1 + k2 + k3;
      Assert.AreEqual(6, kkvChild[2, 3]);
      Assert.IsTrue(_map.ContainsKey(1, 2, 3));
      Assert.IsTrue(kkvChild.ContainsKey(2));
      Assert.IsTrue(kkvChild.ContainsKey(2, 3));
      Assert.IsTrue(kvChild.ContainsKey(3));

      Assert.AreEqual(10, kvChild[7]);
      Assert.IsTrue(_map.ContainsKey(1, 2, 7));
      Assert.IsTrue(kkvChild.ContainsKey(2));
      Assert.IsTrue(kkvChild.ContainsKey(2, 7));
      Assert.IsTrue(kvChild.ContainsKey(7));

      _map.DefaultValue = -1;
      Assert.AreEqual(-1, kkvChild[2, 4]);
      Assert.IsFalse(_map.ContainsKey(1, 3, 4));
      Assert.IsTrue(kkvChild.ContainsKey(2));
      Assert.IsFalse(kkvChild.ContainsKey(2, 4));
      Assert.IsFalse(kvChild.ContainsKey(4));

      Assert.AreEqual(-1, kvChild[5]);
      Assert.IsFalse(_map.ContainsKey(1, 2, 5));
      Assert.IsTrue(kkvChild.ContainsKey(2));
      Assert.IsFalse(kkvChild.ContainsKey(2, 5));
      Assert.IsFalse(kvChild.ContainsKey(5));
    }

    [TestMethod]
    public void InvalidKeyNotFoundBehaviorTest()
    {
      _map.KeyNotFoundBehavior = KeyNotFoundBehavior.DefaultValue | KeyNotFoundBehavior.DefaultGeneration;
      TestUtils.AssertExceptionThrown<ArgumentOutOfRangeException>(() => { int i = _map[1, 2, 3]; });
    }

    [TestMethod]
    public void CountTest()
    {
      Assert.AreEqual(0, _map.Count);

      _map[1, 2, 3] = 4;
      Assert.AreEqual(1, _map.Count);

      _map[1, 2, 3] = 5;
      Assert.AreEqual(1, _map.Count);

      _map[1, 2, 4] = 6;
      Assert.AreEqual(2, _map.Count);

      _map[2, 2, 3] = 7;
      Assert.AreEqual(3, _map.Count);

      _map[1, 3, 3] = 4;
      Assert.AreEqual(4, _map.Count);

      _map[2, 2, 3] = 1;
      Assert.AreEqual(4, _map.Count);
    }

    [TestMethod]
    public void ClearTest()
    {
      Assert.AreEqual(0, _map.Count);

      _map.Clear();
      Assert.AreEqual(0, _map.Count);

      _map[1, 2, 3] = 4;
      Assert.AreEqual(1, _map.Count);

      _map.Clear();
      Assert.AreEqual(0, _map.Count);

      _map[1, 2, 3] = 1;
      _map[1, 2, 4] = 2;
      _map[1, 3, 3] = 3;
      _map[1, 3, 4] = 4;
      _map[4, 2, 3] = 5;
      Assert.AreEqual(5, _map.Count);

      _map.Clear();
      Assert.AreEqual(0, _map.Count);
    }

    [TestMethod]
    public void RemoveLayerOneTest()
    {
      Assert.AreEqual(0, _map.Count);

      RemoveTestHelper(0, false, 0);
      RemoveTestHelper(1, false, 0);
      RemoveTestHelper(new Random().Next(), false, 0);

      _map[1, 2, 3] = 4;
      RemoveTestHelper(0, false, 1);
      RemoveTestHelper(1, true, 0);
      RemoveTestHelper(1, false, 0);

      _map[1, 2, 3] = 1;
      _map[1, 2, 4] = 2;
      _map[1, 3, 3] = 3;
      _map[1, 3, 4] = 4;
      _map[4, 2, 3] = 5;

      RemoveTestHelper(0, false, 5);
      RemoveTestHelper(2, false, 5);
      RemoveTestHelper(3, false, 5);
      RemoveTestHelper(4, true, 4);
      RemoveTestHelper(5, false, 4);
      RemoveTestHelper(6, false, 4);
      RemoveTestHelper(1, true, 0);
    }

    private void RemoveTestHelper(int k1, bool passes, int resultCount)
    {
      Assert.AreEqual(passes, _map.Remove(k1));
      Assert.AreEqual(resultCount, _map.Count);
    }

    [TestMethod]
    public void ForEachTest()
    {
      foreach (var kvp in _map)
      {
        Assert.Fail("unexpected kvp");
      }

      _map[1, 2, 3] = 4;

      foreach (var kvp in _map)
      {
        Assert.AreEqual(1, kvp.Key.Item1);
        Assert.AreEqual(2, kvp.Key.Item2);
        Assert.AreEqual(3, kvp.Key.Item3);
        Assert.AreEqual(4, kvp.Value);
      }
    }

    [TestMethod]
    public void KeysTest()
    {
      var keys = _map.Keys;
      Assert.AreEqual(0, keys.Count());

      _map[1, 2, 3] = 4;
      Assert.AreEqual(1, keys.Count());
      Assert.AreEqual(1, keys.First().Item1);
      Assert.AreEqual(2, keys.First().Item2);
      Assert.AreEqual(3, keys.First().Item3);
    }

    [TestMethod]
    public void ValuesTest()
    {
      var values = _map.Values;
      Assert.AreEqual(0, values.Count());

      _map[1, 2, 3] = 4;
      Assert.AreEqual(1, values.Count());
      Assert.AreEqual(4, values.First());
    }

    [TestMethod]
    public void RemoveTest()
    {
      Assert.IsFalse(_map.Remove(1));
      Assert.IsFalse(_map.Remove(1, 2));
      Assert.IsFalse(_map.Remove(1, 2, 3));

      _map[1, 2, 3] = 4;
      Assert.IsTrue(_map.Remove(1, 2, 3));
      Assert.AreEqual(0, _map.Count);
      Assert.IsFalse(_map.ContainsKey(1));
      Assert.IsFalse(_map.ContainsKey(1, 2));
      Assert.IsFalse(_map.ContainsKey(1, 2, 3));

      _map[1, 2, 3] = 4;
      Assert.IsTrue(_map.Remove(1, 2));
      Assert.AreEqual(0, _map.Count);
      Assert.IsFalse(_map.ContainsKey(1));
      Assert.IsFalse(_map.ContainsKey(1, 2));
      Assert.IsFalse(_map.ContainsKey(1, 2, 3));

      _map[1, 2, 3] = 4;
      Assert.IsTrue(_map.Remove(1));
      Assert.AreEqual(0, _map.Count);
      Assert.IsFalse(_map.ContainsKey(1));
      Assert.IsFalse(_map.ContainsKey(1, 2));
      Assert.IsFalse(_map.ContainsKey(1, 2, 3));

      _map[1, 1, 1] = 4;
      _map[1, 1, 2] = 4;
      _map[1, 2, 1] = 4;
      Assert.AreEqual(3, _map.Count);
      Assert.IsTrue(_map.ContainsKey(1, 1));
      Assert.IsTrue(_map.ContainsKey(1, 2));
      Assert.IsTrue(_map.Remove(1, 1));
      Assert.AreEqual(1, _map.Count);
      Assert.IsFalse(_map.ContainsKey(1, 1));
      Assert.IsTrue(_map.ContainsKey(1, 2));
      Assert.IsTrue(_map.Remove(1));
      Assert.AreEqual(0, _map.Count);

      _map[1, 1, 1] = 4;
      _map[1, 1, 2] = 4;
      _map[1, 2, 1] = 4;
      Assert.AreEqual(3, _map.Count);
      Assert.IsTrue(_map.ContainsKey(1, 1));
      Assert.IsTrue(_map.ContainsKey(1, 2));
      Assert.IsTrue(_map.Remove(1, 2, 1));
      Assert.AreEqual(2, _map.Count);
      Assert.IsTrue(_map.ContainsKey(1, 1));
      Assert.IsTrue(_map.ContainsKey(1, 1, 2));
      Assert.IsFalse(_map.ContainsKey(1, 2, 1));
      Assert.IsFalse(_map.ContainsKey(1, 2));
      Assert.IsTrue(_map.Remove(1));
      Assert.AreEqual(0, _map.Count);

      _map[1, 1, 1] = 1;
      _map[2, 2, 2] = 2;
      Assert.IsTrue(_map.Remove(2, 2, 2));
      Assert.AreEqual(1, _map.Count);
      Assert.IsTrue(_map.ContainsKey(1));
      Assert.IsTrue(_map.ContainsKey(1, 1));
      Assert.IsTrue(_map.ContainsKey(1, 1, 1));
      Assert.IsTrue(_map.Remove(1));
      Assert.AreEqual(0, _map.Count);

      _map[1, 1, 1] = 1;
      _map[2, 2, 2] = 2;
      Assert.IsTrue(_map.Remove(2, 2));
      Assert.AreEqual(1, _map.Count);
      Assert.IsTrue(_map.ContainsKey(1));
      Assert.IsTrue(_map.ContainsKey(1, 1));
      Assert.IsTrue(_map.ContainsKey(1, 1, 1));

      Assert.IsTrue(_map.Remove(1));
      Assert.AreEqual(0, _map.Count);
    }

    [TestMethod]
    public void BasicUseTest()
    {
      var map = new Map<byte, int, long, string>();

      map[0x01, 2, 3L] = "6";
      map[0x01, 3, 3L] = "7";

      Assert.AreEqual("6", map[0x01, 2, 3L], "map check 1");

      map.Remove(0x01, 2, 3L);

      var kvs = map.KeyValues;

      Assert.AreEqual(1, kvs.Count(), "kvs count");
      Assert.AreEqual("7", kvs.First().Value, "kvs[0] value");
      Assert.AreEqual(0x1, kvs.First().Key.Item1, "key 1");
      Assert.AreEqual(3, kvs.First().Key.Item2, "key 2");
      Assert.AreEqual(3L, kvs.First().Key.Item3, "key 3");
    }

    [TestMethod]
    public void SmartCtorTest()
    {
      var list = new List<string>() { "one", "two" };

      var map = new Map<int, int, string, string>(list,
        item => item.Length, item => item.GetHashCode(), item => item.ToUpper());

      Assert.AreEqual("one", map[3, "one".GetHashCode(), "ONE"], "one");
      Assert.AreEqual("two", map[3, "two".GetHashCode(), "TWO"], "one");

      map.DefaultGeneration = (k1, k2, k3) =>
      {
        return k3.ToLower();
      };

      Assert.AreEqual("word", map[1, 1, "WORD"], "word");
      Assert.IsTrue(map.ContainsKey(1, 1, "WORD"), "contains word");

      map.DefaultValue = "null";

      Assert.AreEqual("null", map[1, 1, "1"], "1");

      var kvs = map.KeyValues;
      var kv = kvs.First();
    }

    [TestMethod]
    public void ChildTest()
    {
      var dailyCache = new Map<long, Type, long, object>();

      dailyCache[0, typeof(int), 0] = 0.0m;
      dailyCache[0, typeof(int), 1] = 0.1m;
      dailyCache[0, typeof(int), 2] = 0.2m;

      Assert.IsFalse(dailyCache.ContainsKey(1), "contains day 1");
      Assert.IsFalse(dailyCache.ContainsKey(0, typeof(string)), "contains string");
      Assert.IsFalse(dailyCache.ContainsKey(0, typeof(int), 3), "contains 3");

      var day0Ints = dailyCache[0, typeof(int)];
      day0Ints[3] = 0.3m;

      Assert.AreEqual(0.3m, dailyCache[0, typeof(int), 3], "child set item 3");

      var day0 = dailyCache[0];

      day0[typeof(string), 0] = "0";

      Assert.AreEqual("0", dailyCache[0, typeof(string), 0], "new string cache set by day0");

      var day1 = new Map<Type, long, object>();

      day1[typeof(int), 0] = 0.1m;
    }

    [TestMethod]
    public void CopyCtor()
    {
      var map = new Map<int, int, int, string>();
      map[10, 2, 1] = "10";
      map[15, 3, 1] = "15";

      var copy = new Map<int, int, int, string>(map);

      Assert.AreEqual(map[10, 2, 1], copy[10, 2, 1], "10, 2, 1");
      Assert.AreEqual(map[15, 3, 1], copy[15, 3, 1], "15, 3, 1");

      copy[20, 4, 2] = "20";
      map.Remove(10, 2, 1);

      Assert.IsFalse(map.ContainsKey(20, 4, 2), "20");
      Assert.IsTrue(copy.ContainsKey(10, 2, 1), "10");
    }

    class Thing
    {
      public int Foo { get; set; }
      public int Bar { get; set; }
      public int Boo { get; set; }
      public string Baz { get; set; }
    }

    [TestMethod]
    public void ListToMapTest()
    {
      var things = new List<Thing>
                 {
                     new Thing {Foo = 3, Bar = 3, Boo = 1, Baz = "quick"},
                     new Thing {Foo = 3, Bar = 4, Boo = 1, Baz = "brown"},
                     new Thing {Foo = 6, Bar = 3, Boo = 1, Baz = "fox"},
                     new Thing {Foo = 6, Bar = 3, Boo = 2, Baz = "jumps"}
                 };

      var thingMap = Map<int, int, int, string>.From(things, t => t.Foo, t => t.Bar, t => t.Boo, t => t.Baz);

      Assert.IsTrue(thingMap.ContainsKey(3, 4, 1));
      Assert.AreEqual("brown", thingMap[3, 4, 1]);

      thingMap.DefaultValue = string.Empty;
      Assert.AreEqual("brown", thingMap[3, 4, 1]);
      Assert.AreEqual(string.Empty, thingMap[3, 6, 3]);
    }
  }
}
